<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>this</title>
</head>

<body>
  <script>
    /* 
      1.强制绑定，当函数调用 call apply bind 时，this 指向第一个参数
      2.new 调用，当函数被 new 关键字调用时，this 指向实例化对象
      3.上下文对象调用，当函数被上下文对象调用时，this 指向上下文对象（隐式丢失）
      4.默认调用函数的 this 严格模式下指向 undefined 非严格模式下指向 window
      5.在全局中 this 指向 window
    */

    /* 
      1.call apply bind 都可以改变函数的 this 指向(箭头函数除外)

      2.call 函数的第一个参数为 this 指向的对象，后面的参数作为调用 call 函数的实参(会直接调用函数)

      3.apply 函数的第一参数为 this 指向的对象，第二个参数为数组，数组中的值作为调用 apply 函数的实参(会直接调用函数)

      4.bind 函数的第一参数为 this 指向的对象，后面的参数作为调用 bind 函数的实参(实参也可以写在返回的新函数上)(不会直接调用函数，而是返回一个新函数)
    */

    Function.prototype.customCall = function (content, ...args) {
      // content 类型为 undefined null content 值为 window 
      if (content === undefined || content === null) content = window
      // content 类型为包装类型，content 值为其包装对象
      switch (typeof content) {
        case 'number':
          content = new Number(content);
          break;
        case 'string':
          content = new String(content);
          break;
        case 'boolean':
          content = new Boolean(content);
          break;
      }
      // 在 content 上扩展一个独一无二的方法，该方法为调用 customCall 的方法
      const key = Symbol('customCall')
      content[key] = this
      // 调用函数拿到结
      const result = content[key](...args)
      // 删除该方法
      Reflect.deleteProperty(content, key)
      // 返回结果
      return result
    }

    /* const obj = {
      name: 'tom',
      age: 18
    }

    function info(gender) {
      console.log(this);
      console.log(gender);
    }

    info.customCall(obj, '男') */

    Function.prototype.customApply = function (content, args) { // 与 call 相比传参方式不同（数组）
      if (content === undefined || content === null) content = window
      switch (typeof content) {
        case 'number':
          content = new Number(content);
          break;
        case 'string':
          content = new String(content);
          break;
        case 'boolean':
          content = new Boolean(content);
          break;
      }
      const key = Symbol('customApply')
      content[key] = this
      const result = content[key](...args)
      Reflect.deleteProperty(content, key)
      return result
    }

    /* function info(gender) {
      console.log(this);
      console.log(gender);
    }

    info.customApply(true, ['男']) */

    Function.prototype.customBind = function (content, ...args) {
      return () => {
        return this.customCall(content, ...args)
      }
    }

    function info(gender) {
      console.log(this);
      console.log(gender);
    }

    info.customBind('1', '男')()
  </script>
</body>

</html>